package com.cobra.minio.config;

import cn.hutool.core.util.StrUtil;
import com.google.common.collect.HashMultimap;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import io.minio.messages.Part;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author cobra
 * @date 2021年12月06日 9:44
 */
@Setter
@Getter
@Slf4j
@Component
public class MinIoTemplate implements InitializingBean {

    @Value("${cmp.market.minio.host}")
    private String host;

    @Value("${cmp.market.minio.accessKey}")
    private String accessKey;

    @Value("${cmp.market.minio.secretKey}")
    private String secretKey;

    @Value("${cmp.market.minio.bucketName}")
    private String bucketName;

    /**
     * 默认过期时间(分钟)
     */
    private final static Integer DEFAULT_EXPIRY = 60;

    private CustomMinioClient customMinioClient;

    @Override
    public void afterPropertiesSet() throws Exception {
        MinioClient minioClient = MinioClient.builder().endpoint(host).credentials(accessKey, secretKey).build();
        customMinioClient = new CustomMinioClient(minioClient);
    }


    /**
     * 创建bucketName
     */
    public Boolean createBucket() throws Exception {
        boolean bucketExists = customMinioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        if (!bucketExists) {
            customMinioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
        return Boolean.TRUE;
    }

    /**
     * 上传文件
     *
     * @param inputStream
     * @throws Exception
     */
    public void putObject(String objectName, InputStream inputStream) throws Exception {
        customMinioClient.putObject(PutObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .stream(inputStream, inputStream.available(), -1)
                .build());
    }


    /**
     * 删除单个文件
     *
     * @param objectName
     * @throws Exception
     */
    public void removeObject(String objectName) throws Exception {
        customMinioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }


    /**
     * 删除多个文件
     *
     * @param objectNames
     */
    public void removeObjects(List<String> objectNames) throws Exception {
        List<DeleteObject> objects = objectNames.stream().map(objectName -> new DeleteObject(objectName)).collect(Collectors.toList());
        Iterable<Result<DeleteError>> results = customMinioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());
        for (Result<DeleteError> result : results) {
            DeleteError error = result.get();
            System.out.println("Error in deleting object " + error.objectName() + "; " + error.message());
        }
    }


    /**
     * 获取下载url
     */
    public String getDownloadUrl(String objectName) throws Exception {
        return customMinioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucketName)
                        .object(objectName)
                        .expiry(2, TimeUnit.HOURS)
                        .build());
    }


    /**
     * 下载指定文件
     *
     * @param objectName 欲下载文件对象的名称
     * @throws Exception
     */
    public void downloadObject(String objectName, String targetName, HttpServletResponse response) throws Exception {
        response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(targetName, "UTF-8"));
        response.setContentType("application/force-download");

        ServletOutputStream outputStream = response.getOutputStream();
        InputStream stream = customMinioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
        byte[] buf = new byte[1024];
        int bytesRead;
        while ((bytesRead = stream.read(buf)) > 0) {
            outputStream.write(buf, 0, bytesRead);
        }
        stream.close();
        outputStream.close();
    }

    /**
     * 判断对象是否在minio中是否存在
     *
     * @param objectName
     * @return
     * @throws Exception
     */
    public Boolean isObjectExist(String objectName) throws Exception {
        Iterable<Result<Item>> results = customMinioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
        for (Result<Item> result : results) {
            Item item = result.get();
            if (objectName.equals(item.objectName())) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }

    /**
     * 单文件签名上传
     *
     * @param objectName 文件全路径名称
     * @return /
     */
    public String getUploadObjectUrl(String objectName) {
        try {
            return customMinioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.PUT)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(1, TimeUnit.DAYS)
                            .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 初始化分片上传
     *
     * @param objectName  文件全路径名称
     * @param totalChunks 分片数量
     * @param contentType 类型，如果类型使用默认流会导致无法预览
     * @return /
     */
    public Map<String, String> initMultiPartUpload(String objectName, String uploadId, int totalChunks, String contentType) {
        Map<String, String> partMap = new LinkedHashMap<>();
        try {
            if (StrUtil.isBlank(contentType)) {
                contentType = "application/octet-stream";
            }
            Map<String, String> reqParams = new HashMap<>();
            reqParams.put("uploadId", uploadId);
            for (int i = 1; i <= totalChunks; i++) {
                reqParams.put("partNumber", String.valueOf(i));
                String uploadUrl = customMinioClient.getPresignedObjectUrl(
                        GetPresignedObjectUrlArgs.builder()
                                .method(Method.PUT)
                                .bucket(bucketName)
                                .object(objectName)
                                .expiry(1, TimeUnit.DAYS)
                                .extraQueryParams(reqParams)
                                .build());
                partMap.put(String.valueOf(i), uploadUrl);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return partMap;
    }

    /**
     * 初始化分片上传
     *
     * @param objectName  文件全路径名称
     * @param partNumber  分片数量
     * @param contentType 类型，如果类型使用默认流会导致无法预览
     * @return /
     */
    public String initMultiPartUploadByPartNumber(String objectName, String uploadId, int partNumber, String contentType) {
        try {
            if (StrUtil.isBlank(contentType)) {
                contentType = "application/octet-stream";
            }
            Map<String, String> reqParams = new HashMap<>();
            reqParams.put("uploadId", uploadId);
            reqParams.put("partNumber", String.valueOf(partNumber));
            String uploadUrl = customMinioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.PUT)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(1, TimeUnit.DAYS)
                            .extraQueryParams(reqParams)
                            .build());
            return uploadUrl;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 分片上传完后合并
     *
     * @param objectName 文件全路径名称
     * @param uploadId   返回的uploadId
     * @return /
     */
    public boolean mergeMultipartUpload(String objectName, String uploadId) {
        try {
            //TODO::目前仅做了最大1000分片
            Part[] parts = new Part[1000];
            ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null);
            int partNumber = 1;
            for (Part part : partResult.result().partList()) {
                parts[partNumber - 1] = new Part(partNumber, part.etag());
                partNumber++;
            }
            customMinioClient.mergeMultipartUpload(bucketName, null, objectName, uploadId, parts, null, null);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 查询minio 已上传了那些分片
     *
     * @param objectName
     * @param uploadId
     * @return
     * @throws Exception
     */
    public ListPartsResponse listMultipart(String objectName, String uploadId) throws Exception {
        ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null);
        partResult.result().partList().stream().forEach(x -> {
            log.info("etag:{}  partNumber:{}", x.etag(), x.partNumber());
        });
        return partResult;
    }

    /**
     * 分片上传获取uploadId
     *
     * @return
     */
    public String getMultipartUploadId(String objectName, String contentType) throws Exception {
        if (StrUtil.isBlank(contentType)) {
            contentType = "application/octet-stream";
        }
        HashMultimap<String, String> headers = HashMultimap.create();
        headers.put("Content-Type", contentType);
        String uploadId = customMinioClient.initMultiPartUpload(bucketName, null, objectName, headers, null);
        return uploadId;
    }

    /**
     * 将分钟数转换为秒数
     *
     * @param expiry 过期时间(分钟数)
     * @return expiry
     */
    private static int expiryHandle(Integer expiry) {
        expiry = expiry * 60;
        if (expiry > 604800) {
            return 604800;
        }
        return expiry;
    }
}
